import type { Directive } from 'vue'
import { nextTick } from 'vue'
import {
  ElLoading,
  type LoadingOptions,
} from 'element-plus'

export interface VLoadingOptions {
  /** 空间 */
  space?: string
  /** 目标节点 */
  target?: (() => HTMLElement | Element) | string
}
type SpaceType = VLoadingOptions['space']
type DefineLoadingOpeions = Omit<
  LoadingOptions,
  'target' | 'body' | 'customClass'
> &
Pick<VLoadingOptions, 'target'> & {
  /**
   * 请求时间大于多长时间才出现动画
   * @default 60
   */
  time?: number
  /**
   * 对于开启“destroy-on-close”属性的弹窗内使用应开启此项
   * @default false
   */
  destroyNode?: boolean
}

const defaultSpace: SpaceType = 'default'
const identifying = Symbol('用于记录加载状态')

// 使用查看 @/views/test1.vue文件
function useDefineLoading(opeions: DefineLoadingOpeions = {}) {
  // ------------------use------------------
  // #region
  /** 存放每个空间的加载状态 */
  const spaceLoading = ref<Record<string, boolean>>({})

  /** 需要加载动画的节点 */
  const elMap: Map<
    SpaceType,
    Set<HTMLElement | Element>
  > = new Map()
  /** ElLoading 加载方法实例 */
  const loadingMap: Map<
    SpaceType,
    Set<ReturnType<typeof ElLoading.service>>
  > = new Map()
  // #endregion

  /** 记录是否处于加载中 */
  const loading = computed(() => {
    const allStart = Object.values(spaceLoading.value)
    if (allStart.length === 0) {
      return false
    }
    else {
      return allStart.every(item => item)
    }
  })

  /** 显示加载动画动画 */
  const showLoading = async (space: SpaceType) => {
    // await sleep(30);
    let loadingSet = loadingMap.get(space)
    if (!loadingSet) {
      const _set = new Set([])
      loadingMap.set(space, _set)
      loadingSet = _set
    }
    const els = elMap.get(space)
    if (!els)
      return;
    [...els].forEach((el: any) => {
      const state = Reflect.getMetadata(identifying, el)

      // !说明已经注册
      if (state)
        return
      const loading = ElLoading.service({
        ...opeions,
        target: el || document.documentElement,
        customClass: 'global-loading',
      })

      loadingSet?.add?.(loading)
      // ! 标记状态=>已完成数目与注册对象数量一致即可全部关闭
      Reflect.defineMetadata(identifying, 0, loading)

      if (!el)
        return
      // 标记已经注册
      Reflect.defineMetadata(identifying, true, el)
    })
  }
  /** 当前(或全局)空间内请求是否完成 */
  const isComplete = (space: SpaceType) => {
    const loadingSet = loadingMap.get(space)
    if (
      !loadingSet
      || (loadingSet && loadingSet.size === 0)
    ) {
      return false
    }
    const i = Reflect.getMetadata(
      identifying,
      [...loadingSet!][0]!,
    )
    // ! 减一 ：是因为先做判断再做累加操作，所以需要减一
    return i === loadingSet.size - 1
  }

  const map2Array = <T extends Map<any, Set<any>>>(
    map: T,
  ) => [...map.values()].map(item => [...item]).flat()

  /** 关闭动画 */
  const hideLoading = (space?: SpaceType) => {
    const state = isComplete(space)
    // 全局且未完成 直接跳出
    if (!space && !state)
      return
    // console.log('到俺了', space, state);

    if (space) {
      const loadingSet = loadingMap.get(space)
      const els = elMap.get(space)
      // console.warn(loadingSet, els);

      if (!loadingSet || loadingSet.size === 0)
        return;

      [...loadingSet!].forEach((l) => {
        if (state) {
          l?.close?.()
        }
        else {
          const i = Reflect.getMetadata(identifying, l)
          Reflect.defineMetadata(
            identifying,
            (Number.isNaN(i) ? 0 : i) + 1,
            l,
          )
        }
      })
      if (state) {
        // console.log('清除');
        setSpaceLoading(space, false)
        loadingSet.clear()
        // ! 清除node 节点
        opeions?.destroyNode && elMap.get(space)?.clear?.()
      }
      // ! 取消标记
      if (!els)
        return;
      [...els].forEach(el =>
        Reflect.defineMetadata(identifying, false, el),
      )
      return
    }
    const loadingSets = map2Array(loadingMap)
    const els = map2Array(elMap)
    // 关闭所有动画
    loadingSets.forEach(l => l?.close?.());
    // 清除所有动画set
    [...loadingMap.values()].forEach(m => m.clear?.())
    // ! 取消标记
    els.forEach(el =>
      Reflect.defineMetadata(identifying, false, el),
    )
  }
  function setSpaceLoading(
    space: SpaceType,
    value: boolean,
  ) {
    if (!space)
      return
    Reflect.set(spaceLoading.value, space, value)
    // ! 说明没有使用vLoading 指令
    if ([...elMap.values()].length !== 0)
      return
    let loadingSet = loadingMap.get(space)
    if (!loadingSet) {
      const _set = new Set([])
      loadingMap.set(space, _set)
      loadingSet = _set
    }
    // ! 占位计数
    loadingSet?.add?.((() => 0) as any)
  }
  const getSpaceLoading = (space: SpaceType) => {
    return computed(() =>
      Reflect.get(spaceLoading.value, space ?? ''),
    )
  }
  /** 指令 */
  const vLoading: Directive<HTMLElement, VLoadingOptions>
    = {
      created(_el, binding) {
        const space = binding.value?.space ?? defaultSpace
        let target
          = binding.value?.target ?? opeions?.target
        let selectors = ''
        if (typeof target === 'string') {
          selectors = target
          target = () =>
            document.querySelector(selectors) ?? _el
        }
        let o = elMap.get(space)
        if (!o) {
          const m = new Set<HTMLElement | Element>()
          elMap.set(space, m)
          o = m
        }

        o?.add(target ? target?.() : _el)

        !Reflect.has(spaceLoading.value, space as any)
        && setSpaceLoading(space, false)
      },
    }
  /** */
  const withLoading = <
    T extends (...args: any[]) => Promise<any>,
  >(
      func: T,
      space = defaultSpace,
    ) => {
    /** 定时器 */
    let timeout: NodeJS.Timeout | null = null
    let isExecute = false
    const ok = () => {
      hideLoading(space)
      // !空间状态恢复
      setSpaceLoading(space, false)
      destroyTimeout(space, isExecute)
    }
    /** 销毁定时器 */
    function destroyTimeout(
      space: SpaceType,
      isExecute = false,
    ) {
      clearTimeout(timeout!)
      // ! 判断定时器是否执行
      if (isExecute)
        return
      const loadingSet = loadingMap.get(space)
      if (
        !loadingSet
        || (loadingSet && loadingSet.size === 0)
      ) {
        return
      }
      // ! 完成标记+1
      const loadingArray = [...loadingSet]
      const i = Reflect.getMetadata(
        identifying,
        loadingArray[0]!,
      )
      loadingArray.forEach(l =>
        Reflect.defineMetadata(identifying, i + 1, l),
      )
    }
    const newFunc = async (
      ...params: Parameters<T>
    ): Promise<ReturnType<T>> => {
      await nextTick()
      return new Promise((resolve, reject) => {
        timeout = setTimeout(() => {
          setSpaceLoading(space, true)
          showLoading(space)
          // ! 标记定时器已经执行
          isExecute = true
          // console.log('到这了', func.name, space);
        }, opeions.time ?? 60)
        func(...params)
          .then((result) => {
            resolve(result)
          })
          .catch((err) => {
            reject(err)
          })
          .finally(() => {
            ok()
          })
      })
    }
    return newFunc
  }
  /** 销毁函数 */
  const destroy = async () => {
    hideLoading()
    await nextTick();
    [...elMap.values()].forEach(i => i?.clear?.())
    elMap.clear?.();
    [...loadingMap.values()].forEach(i => i?.clear?.())
    loadingMap.clear?.()
  }

  /** 离开页面销毁 */
  onBeforeUnmount(destroy)

  return {
    vLoading,
    withLoading,
    destroy,
    getSpaceLoading,
    loading,
    spaceLoading,
  }
}

export default useDefineLoading
